"use strict";

const {
    PI,
    cos,
    sin,
    // tan,
    abs,
    // sqrt,
    // pow,
    // min,
    // max,
    // ceil,
    // floor,
    round,
    random,
    atan2
} = Math;
// const HALF_PI = 0.5 * PI;
// const QUART_PI = 0.25 * PI;
const TAU = 2 * PI;
// const TO_RAD = PI / 180;
// const G = 6.67 * pow(10, -11);
// const EPSILON = 2.220446049250313e-16;
const rand = n => n * random();
const randIn = (_min, _max) => rand(_max - _min) + _min;
// const randRange = n => n - rand(2 * n);
const fadeIn = (t, m) => t / m;
// const fadeOut = (t, m) => (m - t) / m;
const fadeInOut = (t, m) => {
    let hm = 0.5 * m;
    return abs((t + hm) % m - hm) / hm;
};
// const dist = (x1, y1, x2, y2) => sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
const angle = (x1, y1, x2, y2) => atan2(y2 - y1, x2 - x1);
const lerp = (a, b, amt) => (1 - amt) * a + amt * b;
// const vh = p => p * window.innerHeight * 0.01;
// const vw = p => p * window.innerWidth * 0.01;
// const vmin = p => min(vh(p), vw(p));
// const vmax = p => max(vh(p), vw(p));
// const clamp = (n, _min, _max) => min(max(n, _min), _max);
// const norm = (n, _min, _max) => (n - _min) / (_max - _min);

Array.prototype.lerp = function (t = [], a = 0) {
    this.forEach((n, i) => (this[i] = lerp(n, t[i], a)));
};

Float32Array.prototype.get = function (i = 0, n = 0) {
    const t = i + n;

    let r = [];

    for (; i < t; i++) {
        r.push(this[i]);
    }

    return r;
};


class PropsArray {
    constructor(count = 0, props = []) {
        this.count = count;
        this.props = props;
        this.values = new Float32Array(count * props.length);
    }
    get length() {
        return this.values.length;
    }
    set(a = [], i = 0) {
        this.values.set(a, i);
    }
    setMap(o = {}, i = 0) {
        this.set(Object.values(o), i);
    }
    get(i = 0) {
        return this.values.get(i, this.props.length);
    }
    getMap(i = 0) {
        return this.get(i).reduce(
            (r, v, i) => ({
                ...r,
                ...{ [this.props[i]]: v }
            }),
            {}
        );
    }
    forEach(cb) {
        let i = 0;

        for (; i < this.length; i += this.props.length) {
            cb(this.get.call(this, i), i, this);
        }
    }
    map(cb) {
        let i = 0;

        for (; i < this.length; i += this.props.length) {
            this.set(cb(this.get.call(this, i), i, this), i);
        }
    }
}

!function () {
    "use strict";
    var r = .5 * (Math.sqrt(3) - 1),
        e = (3 - Math.sqrt(3)) / 6,
        t = 1 / 6,
        a = (Math.sqrt(5) - 1) / 4,
        o = (5 - Math.sqrt(5)) / 20;
    function i(r) {
        var e;
        e = "function" == typeof r ? r : r ?
            function () {
                var r = 0,
                    e = 0,
                    t = 0,
                    a = 1,
                    o = (i = 4022871197,
                        function (r) {
                            r = r.toString();
                            for (var e = 0; e < r.length; e++) {
                                var t = .02519603282416938 * (i += r.charCodeAt(e));
                                t -= i = t >>> 0,
                                    i = (t *= i) >>> 0,
                                    i += 4294967296 * (t -= i)
                            }
                            return 2.3283064365386963e-10 * (i >>> 0)
                        });
                var i;
                r = o(" "),
                    e = o(" "),
                    t = o(" ");
                for (var n = 0; n < arguments.length; n++)(r -= o(arguments[n])) < 0 && (r += 1),
                    (e -= o(arguments[n])) < 0 && (e += 1),
                    (t -= o(arguments[n])) < 0 && (t += 1);
                return o = null,
                    function () {
                        var o = 2091639 * r + 2.3283064365386963e-10 * a;
                        return r = e,
                            e = t,
                            t = o - (a = 0 | o)
                    }
            }(r) : Math.random,
            this.p = n(e),
            this.perm = new Uint8Array(512),
            this.permMod12 = new Uint8Array(512);
        for (var t = 0; t < 512; t++) this.perm[t] = this.p[255 & t],
            this.permMod12[t] = this.perm[t] % 12
    }
    function n(r) {
        var e, t = new Uint8Array(256);
        for (e = 0; e < 256; e++) t[e] = e;
        for (e = 0; e < 255; e++) {
            var a = e + ~~(r() * (256 - e)),
                o = t[e];
            t[e] = t[a],
                t[a] = o
        }
        return t
    }
    i.prototype = {
        grad3: new Float32Array([1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0, 1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, -1, 0, 1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1]),
        grad4: new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1, -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1, 1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1, -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]),
        noise2D: function (t, a) {
            var o, i, n = this.permMod12,
                f = this.perm,
                s = this.grad3,
                v = 0,
                h = 0,
                l = 0,
                u = (t + a) * r,
                d = Math.floor(t + u),
                p = Math.floor(a + u),
                M = (d + p) * e,
                m = t - (d - M),
                c = a - (p - M);
            m > c ? (o = 1, i = 0) : (o = 0, i = 1);
            var y = m - o + e,
                w = c - i + e,
                g = m - 1 + 2 * e,
                A = c - 1 + 2 * e,
                x = 255 & d,
                q = 255 & p,
                D = .5 - m * m - c * c;
            if (D >= 0) {
                var S = 3 * n[x + f[q]];
                v = (D *= D) * D * (s[S] * m + s[S + 1] * c)
            }
            var U = .5 - y * y - w * w;
            if (U >= 0) {
                var b = 3 * n[x + o + f[q + i]];
                h = (U *= U) * U * (s[b] * y + s[b + 1] * w)
            }
            var F = .5 - g * g - A * A;
            if (F >= 0) {
                var N = 3 * n[x + 1 + f[q + 1]];
                l = (F *= F) * F * (s[N] * g + s[N + 1] * A)
            }
            return 70 * (v + h + l)
        },
        noise3D: function (r, e, a) {
            var o, i, n, f, s, v, h, l, u, d, p = this.permMod12,
                M = this.perm,
                m = this.grad3,
                c = (r + e + a) * (1 / 3),
                y = Math.floor(r + c),
                w = Math.floor(e + c),
                g = Math.floor(a + c),
                A = (y + w + g) * t,
                x = r - (y - A),
                q = e - (w - A),
                D = a - (g - A);
            x >= q ? q >= D ? (s = 1, v = 0, h = 0, l = 1, u = 1, d = 0) : x >= D ? (s = 1, v = 0, h = 0, l = 1, u = 0, d = 1) : (s = 0, v = 0, h = 1, l = 1, u = 0, d = 1) : q < D ? (s = 0, v = 0, h = 1, l = 0, u = 1, d = 1) : x < D ? (s = 0, v = 1, h = 0, l = 0, u = 1, d = 1) : (s = 0, v = 1, h = 0, l = 1, u = 1, d = 0);
            var S = x - s + t,
                U = q - v + t,
                b = D - h + t,
                F = x - l + 2 * t,
                N = q - u + 2 * t,
                C = D - d + 2 * t,
                P = x - 1 + .5,
                T = q - 1 + .5,
                _ = D - 1 + .5,
                j = 255 & y,
                k = 255 & w,
                z = 255 & g,
                B = .6 - x * x - q * q - D * D;
            if (B < 0) o = 0;
            else {
                var E = 3 * p[j + M[k + M[z]]];
                o = (B *= B) * B * (m[E] * x + m[E + 1] * q + m[E + 2] * D)
            }
            var G = .6 - S * S - U * U - b * b;
            if (G < 0) i = 0;
            else {
                var H = 3 * p[j + s + M[k + v + M[z + h]]];
                i = (G *= G) * G * (m[H] * S + m[H + 1] * U + m[H + 2] * b)
            }
            var I = .6 - F * F - N * N - C * C;
            if (I < 0) n = 0;
            else {
                var J = 3 * p[j + l + M[k + u + M[z + d]]];
                n = (I *= I) * I * (m[J] * F + m[J + 1] * N + m[J + 2] * C)
            }
            var K = .6 - P * P - T * T - _ * _;
            if (K < 0) f = 0;
            else {
                var L = 3 * p[j + 1 + M[k + 1 + M[z + 1]]];
                f = (K *= K) * K * (m[L] * P + m[L + 1] * T + m[L + 2] * _)
            }
            return 32 * (o + i + n + f)
        },
        noise4D: function (r, e, t, i) {
            var n, f, s, v, h, l, u, d, p, M, m, c, y, w, g, A, x, q = this.perm,
                D = this.grad4,
                S = (r + e + t + i) * a,
                U = Math.floor(r + S),
                b = Math.floor(e + S),
                F = Math.floor(t + S),
                N = Math.floor(i + S),
                C = (U + b + F + N) * o,
                P = r - (U - C),
                T = e - (b - C),
                _ = t - (F - C),
                j = i - (N - C),
                k = 0,
                z = 0,
                B = 0,
                E = 0;
            P > T ? k++ : z++,
                P > _ ? k++ : B++,
                P > j ? k++ : E++,
                T > _ ? z++ : B++,
                T > j ? z++ : E++,
                _ > j ? B++ : E++;
            var G = P - (l = k >= 3 ? 1 : 0) + o,
                H = T - (u = z >= 3 ? 1 : 0) + o,
                I = _ - (d = B >= 3 ? 1 : 0) + o,
                J = j - (p = E >= 3 ? 1 : 0) + o,
                K = P - (M = k >= 2 ? 1 : 0) + 2 * o,
                L = T - (m = z >= 2 ? 1 : 0) + 2 * o,
                O = _ - (c = B >= 2 ? 1 : 0) + 2 * o,
                Q = j - (y = E >= 2 ? 1 : 0) + 2 * o,
                R = P - (w = k >= 1 ? 1 : 0) + 3 * o,
                V = T - (g = z >= 1 ? 1 : 0) + 3 * o,
                W = _ - (A = B >= 1 ? 1 : 0) + 3 * o,
                X = j - (x = E >= 1 ? 1 : 0) + 3 * o,
                Y = P - 1 + 4 * o,
                Z = T - 1 + 4 * o,
                $ = _ - 1 + 4 * o,
                rr = j - 1 + 4 * o,
                er = 255 & U,
                tr = 255 & b,
                ar = 255 & F,
                or = 255 & N,
                ir = .6 - P * P - T * T - _ * _ - j * j;
            if (ir < 0) n = 0;
            else {
                var nr = q[er + q[tr + q[ar + q[or]]]] % 32 * 4;
                n = (ir *= ir) * ir * (D[nr] * P + D[nr + 1] * T + D[nr + 2] * _ + D[nr + 3] * j)
            }
            var fr = .6 - G * G - H * H - I * I - J * J;
            if (fr < 0) f = 0;
            else {
                var sr = q[er + l + q[tr + u + q[ar + d + q[or + p]]]] % 32 * 4;
                f = (fr *= fr) * fr * (D[sr] * G + D[sr + 1] * H + D[sr + 2] * I + D[sr + 3] * J)
            }
            var vr = .6 - K * K - L * L - O * O - Q * Q;
            if (vr < 0) s = 0;
            else {
                var hr = q[er + M + q[tr + m + q[ar + c + q[or + y]]]] % 32 * 4;
                s = (vr *= vr) * vr * (D[hr] * K + D[hr + 1] * L + D[hr + 2] * O + D[hr + 3] * Q)
            }
            var lr = .6 - R * R - V * V - W * W - X * X;
            if (lr < 0) v = 0;
            else {
                var ur = q[er + w + q[tr + g + q[ar + A + q[or + x]]]] % 32 * 4;
                v = (lr *= lr) * lr * (D[ur] * R + D[ur + 1] * V + D[ur + 2] * W + D[ur + 3] * X)
            }
            var dr = .6 - Y * Y - Z * Z - $ * $ - rr * rr;
            if (dr < 0) h = 0;
            else {
                var pr = q[er + 1 + q[tr + 1 + q[ar + 1 + q[or + 1]]]] % 32 * 4;
                h = (dr *= dr) * dr * (D[pr] * Y + D[pr + 1] * Z + D[pr + 2] * $ + D[pr + 3] * rr)
            }
            return 27 * (n + f + s + v + h)
        }
    },
        i._buildPermutationTable = n,
        "undefined" != typeof define && define.amd && define(function () {
            return i
        }),
        "undefined" != typeof exports ? exports.SimplexNoise = i : "undefined" != typeof window && (window.SimplexNoise = i),
        "undefined" != typeof module && (module.exports = i)
}();

"use strict";

const backgroundColor = "rgba(0,0,10,0.5)";
const baseHue = rand(360);
const rangeHue = 180;
const tentacleCount = 30;
const segmentCountMin = 10;
const segmentCountMax = 20;
const segmentLengthMin = 20;
const segmentLengthMax = 40;
const colonyRadius = 200;

let canvas;
let ctx;
let center;
let mouse;
let tick;
let simplex;
let tentacle;
let tentacles;

class Tentacle {
    constructor(x, y, segmentNum, baseLength, baseDirection) {
        this.base = [x, y];
        this.position = [x, y];
        this.target = [x, y];
        this.segmentNum = segmentNum;
        this.baseLength = baseLength;
        this.baseDirection = baseDirection;
        this.segmentProps = ["x1", "y1", "x2", "y2", "l", "d", "h"];
        this.segments = new PropsArray(segmentNum, this.segmentProps);
        this.follow = false;

        let i = this.segments.length - this.segmentProps.length;
        let x1, y1, x2, y2, l, d, h;

        l = this.baseLength;
        d = this.baseDirection;

        for (; i >= 0; i -= this.segmentProps.length) {
            x1 = x2 || this.position[0];
            y1 = y2 || this.position[1];
            x2 = x1 - l * cos(d);
            y2 = y1 - l * sin(d);
            d += 0.309;
            l *= 0.98;
            h = baseHue + fadeIn(i, this.segments.length) * rangeHue;
            this.segments.set([x1, y1, x2, y2, l, d, h], i);
        }
    }
    setCtx(ctx) {
        this.ctx = ctx;
    }
    setTarget(target) {
        this.target = target;
    }
    updateBase() {
        let t = simplex.noise3D(this.base[0] * 0.005, this.base[1] * 0.005, tick * 0.005) * TAU;

        this.base.lerp([
            this.base[0] + 20 * cos(t),
            this.base[1] + 20 * sin(t)],
            0.025);
    }
    async update() {
        let target = this.position;
        let i = this.segments.length - this.segmentProps.length;
        let promises = [];

        this.position.lerp(this.target, 0.015);
        !this.follow && this.updateBase();

        for (; i >= 0; i -= this.segmentProps.length) {
            promises.push(
                new Promise(resolve => {
                    let [x1, y1, x2, y2, l, d, h] = this.segments.get(i);
                    let t, n, tn;

                    x1 = target[0];
                    y1 = target[1];
                    t = angle(x1, y1, x2, y2);
                    n = simplex.noise3D(
                        x1 * 0.005,
                        y1 * 0.005,
                        (i + tick) * 0.005);

                    tn = t + n * PI * 0.0125;
                    x2 = x1 + l * cos(tn);
                    y2 = y1 + l * sin(tn);
                    d = t;

                    target = [x2, y2];

                    this.segments.set([x1, y1, x2, y2, l, d], i);
                    this.drawSegment(x1, y1, x2, y2, h, n, i);

                    resolve();
                }));

        }

        await Promise.all(promises);
    }
    drawSegment(x1, y1, x2, y2, h, n, i) {
        const fn = fadeInOut(1 + n, 2);
        const fa = fadeInOut(i, this.segments.length);
        const a = 0.25 * (fn + fa);

        this.ctx.beginPath();
        this.ctx.strokeStyle = `hsla(${h}, 50%, 50%, ${a})`;
        this.ctx.moveTo(x2, y2);
        this.ctx.lineTo(x1, y1);
        this.ctx.stroke();
        this.ctx.beginPath();

        this.ctx.closePath();
        this.ctx.strokeStyle = `hsla(${h}, 50%, 50%, ${a + 0.5})`;
        this.ctx.arc(x1, y1, fn * 3, 0, TAU);
        this.ctx.stroke();
        this.ctx.closePath();
    }
}


function setup() {
    tick = 0;
    simplex = new SimplexNoise();
    mouse = [0, 0];
    createCanvas();
    resize();
    tentacles = [];

    let i, t;

    for (i = 0; i < tentacleCount; i++) {
        t = i / tentacleCount * TAU;
        tentacle = new Tentacle(
            center[0] + colonyRadius * cos(rand(TAU)),
            center[1] + colonyRadius * sin(rand(TAU)),
            round(randIn(segmentCountMin, segmentCountMax)),
            round(randIn(segmentLengthMin, segmentLengthMax)),
            t);

        tentacle.setCtx(ctx.a);
        tentacles.push(tentacle);
    }

    loop();
}

function createCanvas() {
    canvas = {
        a: document.createElement("canvas"),
        b: document.createElement("canvas")
    };

    canvas.b.style = `
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
`;
    document.body.appendChild(canvas.b);
    ctx = {
        a: canvas.a.getContext("2d"),
        b: canvas.b.getContext("2d")
    };

    center = [0.5 * canvas.a.width, 0.5 * canvas.a.height];
}

function resize() {
    const { innerWidth, innerHeight } = window;

    canvas.a.width = innerWidth;
    canvas.a.height = innerHeight;

    ctx.a.drawImage(canvas.b, 0, 0);

    canvas.b.width = innerWidth;
    canvas.b.height = innerHeight;

    ctx.b.drawImage(canvas.a, 0, 0);

    center[0] = 0.5 * canvas.a.width;
    center[1] = 0.5 * canvas.a.height;
}

function renderBackground() {
    ctx.a.clearRect(0, 0, canvas.b.width, canvas.b.height);
    ctx.b.fillStyle = backgroundColor;
    ctx.b.fillRect(0, 0, canvas.a.width, canvas.a.height);
}

function renderGlow() {
    ctx.b.save();
    ctx.b.filter = "blur(8px) brightness(200%)";
    ctx.b.globalCompositeOperation = "lighter";
    ctx.b.drawImage(canvas.a, 0, 0);
    ctx.b.restore();

    ctx.b.save();
    ctx.b.filter = "blur(4px) brightness(200%)";
    ctx.b.globalCompositeOperation = "lighter";
    ctx.b.drawImage(canvas.a, 0, 0);
    ctx.b.restore();
}

function renderToScreen() {
    ctx.b.save();
    ctx.b.globalCompositeOperation = "lighter";
    ctx.b.drawImage(canvas.a, 0, 0);
    ctx.b.restore();
}

async function loop() {
    tick++;

    renderBackground();

    await Promise.all(tentacles.map(tentacle => tentacle.update()));

    renderGlow();
    renderToScreen();

    window.requestAnimationFrame(loop);
}
function mousemove_(e){
    tentacles.forEach((tentacle, i) => {
        const t = i / tentacles.length * TAU;
        tentacle.setTarget([e.clientX + colonyRadius * cos(t + tick * 0.05), e.clientY + colonyRadius * sin(t + tick * 0.05)]);
        tentacle.follow = true;
    });
}
function mouseout_(){
    tentacles.forEach(tentacle => {
        tentacle.base = [
            center[0] + colonyRadius * cos(rand(TAU)),
            center[1] + colonyRadius * sin(rand(TAU))];

        tentacle.setTarget(tentacle.base);
        tentacle.follow = false;
    });
}
function start(){
    setup();
    // window.addEventListener("load", setup);
    window.addEventListener("resize", resize);
    window.addEventListener("mousemove", mousemove_);
    window.addEventListener("mouseout", mouseout_);
}
function pause(){
    removeEventListener("resize", resize);
    removeEventListener("mousemove", mousemove_);
    removeEventListener("mouseout", mouseout_);
}
